{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": [
     "pdf-title"
    ]
   },
   "source": [
    "# Network Visualization (TensorFlow)\n",
    "\n",
    "In this notebook we will explore the use of *image gradients* for generating new images.\n",
    "\n",
    "When training a model, we define a loss function which measures our current unhappiness with the model's performance; we then use backpropagation to compute the gradient of the loss with respect to the model parameters, and perform gradient descent on the model parameters to minimize the loss.\n",
    "\n",
    "Here we will do something slightly different. We will start from a convolutional neural network model which has been pretrained to perform image classification on the ImageNet dataset. We will use this model to define a loss function which quantifies our current unhappiness with our image, then use backpropagation to compute the gradient of this loss with respect to the pixels of the image. We will then keep the model fixed, and perform gradient descent *on the image* to synthesize a new image which minimizes the loss.\n",
    "\n",
    "In this notebook we will explore three techniques for image generation:\n",
    "\n",
    "1. **Saliency Maps**: Saliency maps are a quick way to tell which part of the image influenced the classification decision made by the network.\n",
    "2. **Fooling Images**: We can perturb an input image so that it appears the same to humans, but will be misclassified by the pretrained network.\n",
    "3. **Class Visualization**: We can synthesize an image to maximize the classification score of a particular class; this can give us some sense of what the network is looking for when it classifies images of that class.\n",
    "\n",
    "This notebook uses **TensorFlow**; we have provided another notebook which explores the same concepts in PyTorch. You only need to complete one of these two notebooks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "tags": [
     "pdf-ignore"
    ]
   },
   "outputs": [],
   "source": [
    "# As usual, a bit of setup\n",
    "import sys\n",
    "sys.path.append('../')\n",
    "import time, os, json\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import tensorflow as tf\n",
    "\n",
    "from cs231n.classifiers.squeezenet import SqueezeNet\n",
    "from cs231n.data_utils import load_tiny_imagenet\n",
    "from cs231n.image_utils import preprocess_image, deprocess_image\n",
    "from cs231n.image_utils import SQUEEZENET_MEAN, SQUEEZENET_STD\n",
    "\n",
    "%matplotlib inline\n",
    "plt.rcParams['figure.figsize'] = (10.0, 8.0) # set default size of plots\n",
    "plt.rcParams['image.interpolation'] = 'nearest'\n",
    "plt.rcParams['image.cmap'] = 'gray'\n",
    "\n",
    "# for auto-reloading external modules\n",
    "# see http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython\n",
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": [
     "pdf-ignore"
    ]
   },
   "source": [
    "# Pretrained Model\n",
    "\n",
    "For all of our image generation experiments, we will start with a convolutional neural network which was pretrained to perform image classification on ImageNet. We can use any model here, but for the purposes of this assignment we will use SqueezeNet [1], which achieves accuracies comparable to AlexNet but with a significantly reduced parameter count and computational complexity.\n",
    "\n",
    "Using SqueezeNet rather than AlexNet or VGG or ResNet means that we can easily perform all image generation experiments on CPU.\n",
    "\n",
    "We have ported the PyTorch SqueezeNet model to TensorFlow; see: `cs231n/classifiers/squeezenet.py` for the model architecture.\n",
    "\n",
    "To use SqueezeNet, you will need to first **download the weights** by descending into the `cs231n/datasets` directory and running `get_squeezenet_tf.sh`. Note that if you ran `get_assignment3_data.sh` then SqueezeNet will already be downloaded.\n",
    "\n",
    "Once you've downloaded the Squeezenet model, we can load it into a new TensorFlow session:\n",
    "\n",
    "[1] Iandola et al, \"SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and < 0.5MB model size\", arXiv 2016"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "tags": [
     "pdf-ignore"
    ]
   },
   "outputs": [],
   "source": [
    "SAVE_PATH = 'cs231n/datasets/squeezenet.ckpt'\n",
    "\n",
    "if not os.path.exists(SAVE_PATH + \".index\"):\n",
    "    raise ValueError(\"You need to download SqueezeNet!\")\n",
    "\n",
    "model = SqueezeNet()\n",
    "status = model.load_weights(SAVE_PATH)\n",
    "\n",
    "model.trainable = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load some ImageNet images\n",
    "We have provided a few example images from the validation set of the ImageNet ILSVRC 2012 Classification dataset. To download these images, descend into `cs231n/datasets/` and run `get_imagenet_val.sh`.\n",
    "\n",
    "Since they come from the validation set, our pretrained model did not see these images during training.\n",
    "\n",
    "Run the following cell to visualize some of these images, along with their ground-truth labels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from cs231n.data_utils import load_imagenet_val\n",
    "X_raw, y, class_names = load_imagenet_val(num=5)\n",
    "\n",
    "plt.figure(figsize=(12, 6))\n",
    "for i in range(5):\n",
    "    plt.subplot(1, 5, i + 1)\n",
    "    plt.imshow(X_raw[i])\n",
    "    plt.title(class_names[y[i]])\n",
    "    plt.axis('off')\n",
    "plt.gcf().tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preprocess images\n",
    "The input to the pretrained model is expected to be normalized, so we first preprocess the images by subtracting the pixelwise mean and dividing by the pixelwise standard deviation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "X = np.array([preprocess_image(img) for img in X_raw])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Saliency Maps\n",
    "Using this pretrained model, we will compute class saliency maps as described in Section 3.1 of [2].\n",
    "\n",
    "A **saliency map** tells us the degree to which each pixel in the image affects the classification score for that image. To compute it, we compute the gradient of the unnormalized score corresponding to the correct class (which is a scalar) with respect to the pixels of the image. If the image has shape `(H, W, 3)` then this gradient will also have shape `(H, W, 3)`; for each pixel in the image, this gradient tells us the amount by which the classification score will change if the pixel changes by a small amount. To compute the saliency map, we take the absolute value of this gradient, then take the maximum value over the 3 input channels; the final saliency map thus has shape `(H, W)` and all entries are nonnegative.\n",
    "\n",
    "Open the file `cs231n/classifiers/squeezenet.py` and read the code to make sure you understand how to use the model. You will have to use [`tf.GradientTape()`](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/GradientTape) to compute gradients with respect to the pixels of the image. In particular, it will be very useful to read this [section](https://www.tensorflow.org/alpha/tutorials/eager/automatic_differentiation#gradient_tapes) for better understanding.\n",
    "\n",
    "[2] Karen Simonyan, Andrea Vedaldi, and Andrew Zisserman. \"Deep Inside Convolutional Networks: Visualising\n",
    "Image Classification Models and Saliency Maps\", ICLR Workshop 2014."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Hint: Tensorflow `gather_nd` method\n",
    "Recall in Assignment 1 you needed to select one element from each row of a matrix; if `s` is an numpy array of shape `(N, C)` and `y` is a numpy array of shape `(N,`) containing integers `0 <= y[i] < C`, then `s[np.arange(N), y]` is a numpy array of shape `(N,)` which selects one element from each element in `s` using the indices in `y`.\n",
    "\n",
    "In Tensorflow you can perform the same operation using the `gather_nd()` method. If `s` is a Tensor of shape `(N, C)` and `y` is a Tensor of shape `(N,)` containing longs in the range `0 <= y[i] < C`, then\n",
    "\n",
    "`tf.gather_nd(s, tf.stack((tf.range(N), y), axis=1))`\n",
    "\n",
    "will be a Tensor of shape `(N,)` containing one entry from each row of `s`, selected according to the indices in `y`.\n",
    "\n",
    "You can also read the documentation for the [gather_nd method](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/gather_nd)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def compute_saliency_maps(X, y, model):\n",
    "    \"\"\"\n",
    "    Compute a class saliency map using the model for images X and labels y.\n",
    "\n",
    "    Input:\n",
    "    - X: Input images, numpy array of shape (N, H, W, 3)\n",
    "    - y: Labels for X, numpy of shape (N,)\n",
    "    - model: A SqueezeNet model that will be used to compute the saliency map.\n",
    "\n",
    "    Returns:\n",
    "    - saliency: A numpy array of shape (N, H, W) giving the saliency maps for the\n",
    "    input images.\n",
    "    \"\"\"\n",
    "    saliency = None\n",
    "    # Compute the score of the correct class for each example.\n",
    "    # This gives a Tensor with shape [N], the number of examples.\n",
    "    #\n",
    "    # Note: this is equivalent to scores[np.arange(N), y] we used in NumPy\n",
    "    # for computing vectorized losses.\n",
    "    \n",
    "    ###############################################################################\n",
    "    # TODO: Produce the saliency maps over a batch of images.                     #\n",
    "    #                                                                             #\n",
    "    # 1) Define a gradient tape object and watch input Image variable             #\n",
    "    # 2) Compute the \u201closs\u201d for the batch of given input images.                  #\n",
    "    #    - get scores output by the model for the given batch of input images     #\n",
    "    #    - use tf.gather_nd or tf.gather to get correct scores                    #\n",
    "    # 3) Use the gradient() method of the gradient tape object to compute the     #\n",
    "    #    gradient of the loss with respect to the image                           #\n",
    "    # 4) Finally, process the returned gradient to compute the saliency map.      #\n",
    "    ###############################################################################\n",
    "    # *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n\n",
    "    pass\n",
    "\n    # *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n",
    "    ##############################################################################\n",
    "    #                             END OF YOUR CODE                               #\n",
    "    ##############################################################################\n",
    "    return saliency"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once you have completed the implementation in the cell above, run the following to visualize some class saliency maps on our example images from the ImageNet validation set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "tags": [
     "pdf-ignore-input"
    ]
   },
   "outputs": [],
   "source": [
    "def show_saliency_maps(X, y, mask):\n",
    "    mask = np.asarray(mask)\n",
    "    Xm = X[mask]\n",
    "    ym = y[mask]\n",
    "\n",
    "    saliency = compute_saliency_maps(Xm, ym, model)\n",
    "\n",
    "    for i in range(mask.size):\n",
    "        plt.subplot(2, mask.size, i + 1)\n",
    "        plt.imshow(deprocess_image(Xm[i]))\n",
    "        plt.axis('off')\n",
    "        plt.title(class_names[ym[i]])\n",
    "        plt.subplot(2, mask.size, mask.size + i + 1)\n",
    "        plt.title(mask[i])\n",
    "        plt.imshow(saliency[i], cmap=plt.cm.hot)\n",
    "        plt.axis('off')\n",
    "        plt.gcf().set_size_inches(10, 4)\n",
    "    plt.show()\n",
    "\n",
    "mask = np.arange(5)\n",
    "show_saliency_maps(X, y, mask)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": [
     "pdf-inline"
    ]
   },
   "source": [
    "# INLINE QUESTION\n",
    "\n",
    "A friend of yours suggests that in order to find an image that maximizes the correct score, we can perform gradient ascent on the input image, but instead of the gradient we can actually use the saliency map in each step to update the image. Is this assertion true? Why or why not?\n",
    "\n",
    "**Your Answer:** \n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Fooling Images\n",
    "We can also use image gradients to generate \"fooling images\" as discussed in [3]. Given an image and a target class, we can perform gradient **ascent** over the image to maximize the target class, stopping when the network classifies the image as the target class. Implement the following function to generate fooling images.\n",
    "\n",
    "[3] Szegedy et al, \"Intriguing properties of neural networks\", ICLR 2014"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def make_fooling_image(X, target_y, model):\n",
    "    \"\"\"\n",
    "    Generate a fooling image that is close to X, but that the model classifies\n",
    "    as target_y.\n",
    "\n",
    "    Inputs:\n",
    "    - X: Input image, a numpy array of shape (1, 224, 224, 3)\n",
    "    - target_y: An integer in the range [0, 1000)\n",
    "    - model: Pretrained SqueezeNet model\n",
    "\n",
    "    Returns:\n",
    "    - X_fooling: An image that is close to X, but that is classifed as target_y\n",
    "    by the model.\n",
    "    \"\"\"\n",
    "    \n",
    "    # Make a copy of the input that we will modify\n",
    "    X_fooling = X.copy()\n",
    "    \n",
    "    # Step size for the update\n",
    "    learning_rate = 1\n",
    "    \n",
    "    ##############################################################################\n",
    "    # TODO: Generate a fooling image X_fooling that the model will classify as   #\n",
    "    # the class target_y. Use gradient *ascent* on the target class score, using #\n",
    "    # the model.scores Tensor to get the class scores for the model.image.   #\n",
    "    # When computing an update step, first normalize the gradient:               #\n",
    "    #   dX = learning_rate * g / ||g||_2                                         #\n",
    "    #                                                                            #\n",
    "    # You should write a training loop, where in each iteration, you make an     #\n",
    "    # update to the input image X_fooling (don't modify X). The loop should      #\n",
    "    # stop when the predicted class for the input is the same as target_y.       #\n",
    "    #                                                                            #\n",
    "    # HINT: Use tf.GradientTape() to keep track of your gradients and            #\n",
    "    # use tape.gradient to get the actual gradient with respect to X_fooling.    #\n",
    "    #                                                                            #\n",
    "    # HINT 2: For most examples, you should be able to generate a fooling image  #\n",
    "    # in fewer than 100 iterations of gradient ascent. You can print your        #\n",
    "    # progress over iterations to check your algorithm.                          #\n",
    "    ##############################################################################\n",
    "    # *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n\n",
    "    pass\n",
    "\n    # *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n",
    "    ##############################################################################\n",
    "    #                             END OF YOUR CODE                               #\n",
    "    ##############################################################################\n",
    "    return X_fooling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Run the following to generate a fooling image. You should ideally see at first glance no major difference between the original and fooling images, and the network should now make an incorrect prediction on the fooling one. However you should see a bit of random noise if you look at the 10x magnified difference between the original and fooling images. Feel free to change the `idx` variable to explore other images."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "tags": [
     "pdf-ignore-input"
    ]
   },
   "outputs": [],
   "source": [
    "idx = 0\n",
    "Xi = X[idx][None]\n",
    "target_y = 6\n",
    "X_fooling = make_fooling_image(Xi, target_y, model)\n",
    "\n",
    "# Make sure that X_fooling is classified as y_target\n",
    "scores = model(X_fooling)\n",
    "assert tf.math.argmax(scores[0]).numpy() == target_y, 'The network is not fooled!'\n",
    "\n",
    "# Show original image, fooling image, and difference\n",
    "orig_img = deprocess_image(Xi[0])\n",
    "fool_img = deprocess_image(X_fooling[0])\n",
    "plt.figure(figsize=(12, 6))\n",
    "\n",
    "# Rescale \n",
    "plt.subplot(1, 4, 1)\n",
    "plt.imshow(orig_img)\n",
    "plt.axis('off')\n",
    "plt.title(class_names[y[idx]])\n",
    "plt.subplot(1, 4, 2)\n",
    "plt.imshow(fool_img)\n",
    "plt.title(class_names[target_y])\n",
    "plt.axis('off')\n",
    "plt.subplot(1, 4, 3)\n",
    "plt.title('Difference')\n",
    "plt.imshow(deprocess_image((Xi-X_fooling)[0]))\n",
    "plt.axis('off')\n",
    "plt.subplot(1, 4, 4)\n",
    "plt.title('Magnified difference (10x)')\n",
    "plt.imshow(deprocess_image(10 * (Xi-X_fooling)[0]))\n",
    "plt.axis('off')\n",
    "plt.gcf().tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Class visualization\n",
    "By starting with a random noise image and performing gradient ascent on a target class, we can generate an image that the network will recognize as the target class. This idea was first presented in [2]; [3] extended this idea by suggesting several regularization techniques that can improve the quality of the generated image.\n",
    "\n",
    "Concretely, let $I$ be an image and let $y$ be a target class. Let $s_y(I)$ be the score that a convolutional network assigns to the image $I$ for class $y$; note that these are raw unnormalized scores, not class probabilities. We wish to generate an image $I^*$ that achieves a high score for the class $y$ by solving the problem\n",
    "\n",
    "$$\n",
    "I^* = {\\arg\\max}_I (s_y(I) - R(I))\n",
    "$$\n",
    "\n",
    "where $R$ is a (possibly implicit) regularizer (note the sign of $R(I)$ in the argmax: we want to minimize this regularization term). We can solve this optimization problem using gradient ascent, computing gradients with respect to the generated image. We will use (explicit) L2 regularization of the form\n",
    "\n",
    "$$\n",
    "R(I) = \\lambda \\|I\\|_2^2\n",
    "$$\n",
    "\n",
    "**and** implicit regularization as suggested by [3] by periodically blurring the generated image. We can solve this problem using gradient ascent on the generated image.\n",
    "\n",
    "In the cell below, complete the implementation of the `create_class_visualization` function.\n",
    "\n",
    "[2] Karen Simonyan, Andrea Vedaldi, and Andrew Zisserman. \"Deep Inside Convolutional Networks: Visualising\n",
    "Image Classification Models and Saliency Maps\", ICLR Workshop 2014.\n",
    "\n",
    "[3] Yosinski et al, \"Understanding Neural Networks Through Deep Visualization\", ICML 2015 Deep Learning Workshop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "tags": [
     "pdf-ignore"
    ]
   },
   "outputs": [],
   "source": [
    "from scipy.ndimage.filters import gaussian_filter1d\n",
    "def blur_image(X, sigma=1):\n",
    "    X = gaussian_filter1d(X, sigma, axis=1)\n",
    "    X = gaussian_filter1d(X, sigma, axis=2)\n",
    "    return X"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "tags": [
     "pdf-ignore"
    ]
   },
   "outputs": [],
   "source": [
    "def jitter(X, ox, oy):\n",
    "    \"\"\"\n",
    "    Helper function to randomly jitter an image.\n",
    "    \n",
    "    Inputs\n",
    "    - X: Tensor of shape (N, H, W, C)\n",
    "    - ox, oy: Integers giving number of pixels to jitter along W and H axes\n",
    "    \n",
    "    Returns: A new Tensor of shape (N, H, W, C)\n",
    "    \"\"\"\n",
    "    if ox != 0:\n",
    "        left = X[:, :, :-ox]\n",
    "        right = X[:, :, -ox:]\n",
    "        X = tf.concat([right, left], axis=2)\n",
    "    if oy != 0:\n",
    "        top = X[:, :-oy]\n",
    "        bottom = X[:, -oy:]\n",
    "        X = tf.concat([bottom, top], axis=1)\n",
    "    return X"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def create_class_visualization(target_y, model, **kwargs):\n",
    "    \"\"\"\n",
    "    Generate an image to maximize the score of target_y under a pretrained model.\n",
    "    \n",
    "    Inputs:\n",
    "    - target_y: Integer in the range [0, 1000) giving the index of the class\n",
    "    - model: A pretrained CNN that will be used to generate the image\n",
    "    \n",
    "    Keyword arguments:\n",
    "    - l2_reg: Strength of L2 regularization on the image\n",
    "    - learning_rate: How big of a step to take\n",
    "    - num_iterations: How many iterations to use\n",
    "    - blur_every: How often to blur the image as an implicit regularizer\n",
    "    - max_jitter: How much to jitter the image as an implicit regularizer\n",
    "    - show_every: How often to show the intermediate result\n",
    "    \"\"\"\n",
    "    l2_reg = kwargs.pop('l2_reg', 1e-3)\n",
    "    learning_rate = kwargs.pop('learning_rate', 25)\n",
    "    num_iterations = kwargs.pop('num_iterations', 100)\n",
    "    blur_every = kwargs.pop('blur_every', 10)\n",
    "    max_jitter = kwargs.pop('max_jitter', 16)\n",
    "    show_every = kwargs.pop('show_every', 25)\n",
    "    \n",
    "    # We use a single image of random noise as a starting point\n",
    "    X = 255 * np.random.rand(224, 224, 3)\n",
    "    X = preprocess_image(X)[None]\n",
    "\n",
    "    loss = None # scalar loss\n",
    "    grad = None # gradient of loss with respect to model.image, same size as model.image\n",
    "    \n",
    "    X = tf.Variable(X)\n",
    "    for t in range(num_iterations):\n",
    "        # Randomly jitter the image a bit; this gives slightly nicer results\n",
    "        ox, oy = np.random.randint(0, max_jitter, 2)\n",
    "        X = jitter(X, ox, oy)\n",
    "        \n",
    "        ########################################################################\n",
    "        # TODO: Compute the value of the gradient of the score for             #\n",
    "        # class target_y with respect to the pixels of the image, and make a   #\n",
    "        # gradient step on the image using the learning rate. You should use   #\n",
    "        # the tf.GradientTape() and tape.gradient to compute gradients.        #\n",
    "        #                                                                      #\n",
    "        # Be very careful about the signs of elements in your code.            #\n",
    "        ########################################################################\n",
    "        # *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n\n",
    "        pass\n",
    "\n        # *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n",
    "        ############################################################################\n",
    "        #                             END OF YOUR CODE                             #\n",
    "        ############################################################################\n",
    "        \n",
    "        # Undo the jitter\n",
    "        X = jitter(X, -ox, -oy)\n",
    "        # As a regularizer, clip and periodically blur\n",
    "        \n",
    "        X = tf.clip_by_value(X, -SQUEEZENET_MEAN/SQUEEZENET_STD, (1.0 - SQUEEZENET_MEAN)/SQUEEZENET_STD)\n",
    "        if t % blur_every == 0:\n",
    "            X = blur_image(X, sigma=0.5)\n",
    "\n",
    "        # Periodically show the image\n",
    "        if t == 0 or (t + 1) % show_every == 0 or t == num_iterations - 1:\n",
    "            plt.imshow(deprocess_image(X[0]))\n",
    "            class_name = class_names[target_y]\n",
    "            plt.title('%s\\nIteration %d / %d' % (class_name, t + 1, num_iterations))\n",
    "            plt.gcf().set_size_inches(4, 4)\n",
    "            plt.axis('off')\n",
    "            plt.show()\n",
    "    return X"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once you have completed the implementation in the cell above, run the following cell to generate an image of Tarantula:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "target_y = 76 # Tarantula\n",
    "out = create_class_visualization(target_y, model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try out your class visualization on other classes! You should also feel free to play with various hyperparameters to try and improve the quality of the generated image, but this is not required."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "target_y = np.random.randint(1000)\n",
    "# target_y = 78 # Tick\n",
    "# target_y = 187 # Yorkshire Terrier\n",
    "# target_y = 683 # Oboe\n",
    "# target_y = 366 # Gorilla\n",
    "# target_y = 604 # Hourglass\n",
    "print(class_names[target_y])\n",
    "X = create_class_visualization(target_y, model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}